home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / metamail / richmail / richlex.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-10-21  |  12.6 KB  |  462 lines

  1. /*-------------------------------------------------------------------------
  2.  
  3.   richlex.c - Lexical analysis routines for parsing richtext messages.
  4.  
  5.   Copyright (c) 1992 Rhys Weatherley
  6.  
  7.   Permission to use, copy, modify, and distribute this material
  8.   for any purpose and without fee is hereby granted, provided
  9.   that the above copyright notice and this permission notice
  10.   appear in all copies, and that the name of Rhys Weatherley not be
  11.   used in advertising or publicity pertaining to this
  12.   material without specific, prior written permission.
  13.   RHYS WEATHERLEY MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR
  14.   SUITABILITY OF THIS MATERIAL FOR ANY PURPOSE.  IT IS PROVIDED
  15.   "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
  16.  
  17.   Revision History:
  18.   ================
  19.  
  20.    Version  DD/MM/YY  By  Description
  21.    -------  --------  --  --------------------------------------
  22.      1.0    31/01/92  RW  Original Version of richlex.c
  23.      1.1    19/06/92  RW  Add support for multi-byte ISO-2022 codes.
  24.  
  25.   You may contact the author by:
  26.   =============================
  27.  
  28.    e-mail: rhys@cs.uq.oz.au
  29.      mail: Rhys Weatherley
  30.        5 Horizon Drive
  31.        Jamboree Heights
  32.        Queensland 4074
  33.        Australia
  34.  
  35.   Caveats:
  36.   =======
  37.  
  38.   If a multi-byte character contains "<lt>", and a richtext command or
  39.   escape sequence is started before all bytes of the multi-byte character
  40.   have been read in, then some characters may be discarded.
  41.  
  42. -------------------------------------------------------------------------*/
  43.  
  44. #include <stdio.h>
  45. #include <ctype.h>
  46. #include "richlex.h"
  47. #include "richset.h"
  48.  
  49. int CorrectionEnabled = 1;    /* Zero if correction has been disabled */
  50. int RichtextLessThanFlag = 0;    /* Non-zero to turn on multi-byte '<' hack */
  51.  
  52. #ifndef AMIGA
  53. extern    int fgetc ();
  54. extern    int fputc ();
  55. #endif
  56.  
  57. int (*RichtextGetc) () = fgetc;    /* Function to call to get characters */
  58. int (*RichtextPutc) () = fputc; /* Function to call to put characters */
  59. int RichtextCharEncoding = RICH_ENC_US_ASCII; /* Current encoding mode */
  60.  
  61. #define    MAX_STACK_SIZE    500
  62. #define    MAX_FLUSH_SIZE    3
  63. #define    MAX_PUSH_BACK    20
  64. static    int    StackSize=0;
  65. static    char    Stack[MAX_STACK_SIZE][MAX_TOKEN_SIZE];
  66. static    char    NextToken[MAX_TOKEN_SIZE];
  67. static    int    FlushStack=0;
  68. static    int    FlushSize=0;
  69. static    int    EndInpFile=0;
  70. static    int    CharSize=1;
  71. static    int    PushbackBuffer[MAX_PUSH_BACK];
  72. static    int    PushbackSize=0;
  73. static    int    PushbackExtract=0;
  74.  
  75. #define    ESC    033
  76. #define    SI    017
  77. #define    SO    016
  78.  
  79. #define    RGETRAW(f)    ((*RichtextGetc)(f))
  80. #define    RGET(f)        (PushbackSize ? richtextgetback() : RGETRAW(f))
  81. #define    RPUT(c,f)    ((*RichtextPutc)(((int)(c)),(f)))
  82. #define    RUNGET(c)    (richtextunget(c))
  83. #define    RPUSHBACK(c)    (richtextpushback(c))
  84.  
  85. /*
  86.  * Define a "printf" format for a generic ISO-2022 character
  87.  * set name that includes the hexadecimal representation of
  88.  * the escape sequence character that turns ISO-2022 on or off
  89.  * on the terminal.
  90.  */
  91. #define    ISO2022_GENERIC    "x-iso-2022-gen-%2x"
  92. #define    ISO2022_CHARSET "x-iso-charset-"
  93.  
  94. /*
  95.  * Define the character set shift characters for ISO-2022-KR.
  96.  */
  97. #define    ISO2022_SHIFTIN  "x-iso-shift-in"
  98. #define    ISO2022_SHIFTOUT "x-iso-shift-out"
  99.  
  100. /*
  101.  * Construct multi-byte character codes.
  102.  */
  103. #define    RICHCH_2(first,second)    ((RCHAR)(((first) << 8) | (second)))
  104.  
  105. /*
  106.  * Reset the richtext parsing mechanism.
  107.  */
  108. richtextreset()
  109. {
  110.     StackSize = 0;
  111.     FlushStack = 0;
  112.     FlushSize = 0;
  113.     EndInpFile = 0;
  114.     CharSize = 1;
  115.     PushbackSize = 0;
  116.     PushbackExtract = 0;
  117.     RichtextCharEncoding = RICH_ENC_US_ASCII;
  118.     CorrectionEnabled = 1;
  119.     RichtextLessThanFlag = 0;
  120. }
  121.  
  122. /*
  123.  * Push a character into the push-back buffer for later
  124.  * retrieval by RGET.
  125.  */
  126. static    void    richtextpushback(c)
  127. int    c;
  128. {
  129.     PushbackBuffer[PushbackSize++] = c;
  130. }
  131.  
  132. /*
  133.  * Unget a character that has been read from the input stream.
  134.  */
  135. static    void    richtextunget(c)
  136. int    c;
  137. {
  138.     if (PushbackSize)
  139.     --PushbackExtract;    /* Character was retrieved from push-back */
  140.     else
  141.     richtextpushback(c);    /* Put character into empty push-back */
  142. }
  143.  
  144. /*
  145.  * Unget two characters that have been read from the input stream.
  146.  */
  147. static    void    richtextunget2(c1,c2)
  148. int    c1,c2;
  149. {
  150.     if (PushbackExtract > 1) {
  151.     PushbackExtract -= 2;    /* Go back two characters in the push-back */
  152.     PushbackBuffer[PushbackExtract++] = c1;
  153.     PushbackBuffer[PushbackExtract++] = c2;
  154.     } else {
  155.     richtextpushback(c1);    /* Put the characters into the push-back */
  156.     richtextpushback(c2);
  157.     }
  158. }
  159.  
  160. /*
  161.  * Retrieve a character from the push-back buffer.
  162.  */
  163. static    int    richtextgetback()
  164. {
  165.     int c;
  166.     c = PushbackBuffer[PushbackExtract++];
  167.     if (PushbackExtract >= PushbackSize) {
  168.     PushbackSize = 0;
  169.     PushbackExtract = 0;
  170.     }
  171.     return(c);
  172. }
  173.  
  174. /*
  175.  * Find a match between NextToken and an element on the stack.
  176.  * Returns the number of elements down from the top it is.
  177.  * i.e. 0 if not on the stack, 1 if at the top, etc.
  178.  */
  179. static int richtextmatchup()
  180. {
  181.     int i = StackSize;
  182.     while (i > 0 && i > (StackSize - MAX_FLUSH_SIZE)) {
  183.     --i;
  184.     if (!strcmp(NextToken,Stack[i]))
  185.         return(StackSize - i);
  186.     }
  187.     return(0);
  188. }
  189.  
  190. /*
  191.  * Determine if the current token is one of the singleton
  192.  * richtext commands: <nl>, <lt>, <np>.
  193.  */
  194. static richtextsingle()
  195. {
  196.     return (charsetsingle (NextToken) ||
  197.         !strcmp(NextToken,"nl") ||
  198.         !strcmp(NextToken,"lt") ||
  199.         !strcmp(NextToken,"np"));
  200. }
  201.  
  202. /*
  203.  * Recognise a character that can start a richtext command.
  204.  */
  205. #define    iscmdch(c)    (isalpha(c) || isdigit(c) || (c) == '/' || (c) == '-')
  206. #define iscmdch2(c)    (isalpha(c) || isdigit(c) || (c) == '-')
  207. #define TOLOWER(c)    (isupper(c)?tolower(c):c)
  208. #define valid_command(c1,c2) \
  209.     (( c1 == '/' && iscmdch2(c2) ) || \
  210.      ( TOLOWER(c1) == 'l' && TOLOWER(c2) == 't' ))
  211.  
  212. /*
  213.  * Get the next token from the input stream.  RICHTEXT_COMMAND
  214.  * or RICHTEXT_NEG_COMMAND are returned if it is a richtext command.
  215.  * e.g. "<cmd>" or "</cmd>".  The "token" buffer will receive the
  216.  * name of the command (without <,> or /) if it is a command.  This
  217.  * function will also truncate commands longer than MAX_TOKEN_SIZE - 1
  218.  * characters and abort command parsing if white space is encountered,
  219.  * so, for example, errors like "<bold hi kids</bold>" don't cause
  220.  * problems: it will be corrected to "<bold>hi kids</bold>".
  221.  */
  222. RCHAR richtextlex(file,token)
  223. void *file;
  224. char *token;
  225. {
  226.     int c,i,lastch;
  227.     RCHAR cmd;
  228.  
  229.     lastch = 0;        /* No previous character for multi-byte chars as yet */
  230.  
  231.     /* Perform any flushing of balancing commands that is necessary */
  232.     if (FlushStack) {
  233.     /* Flush out some extra closing commands */
  234.     strcpy(token,Stack[StackSize - FlushSize + (--FlushStack)]);
  235.     return(RICHTEXT_NEG_COMMAND);
  236.     } else if (FlushSize) {
  237.     /* Finished flushing: output the pending close command */
  238.     StackSize -= FlushSize;
  239.     if (StackSize > 0)
  240.         --StackSize; /* Remove the command that was being matched up */
  241.     FlushSize = 0;
  242.     strcpy(token,NextToken);
  243.     if (EndInpFile)
  244.         return((RCHAR)EOF); /* The last flush was the end-of-file cleanup */
  245.     else
  246.         return(RICHTEXT_NEG_COMMAND);
  247.     }
  248.  
  249.     /* Fetch a new character or richtext command */
  250.     for (;;) {        /* Loop so we can come back on ignored commands */
  251.         c = RGET(file);
  252.         if (c == '<') { 
  253.         /* Check for multi-byte mode, where "<" is special */
  254.         int c2;
  255.         if (CharSize > 1) {
  256.         if (RichtextLessThanFlag) {
  257.             /* The multi-byte '<' hack is in effect: not a command */
  258.             if (lastch)
  259.                 return(RICHCH_2(lastch,'<'));
  260.             lastch = '<';
  261.             continue;    /* Back around for another character */
  262.         }
  263.         if ((c = RGET(file)) == EOF) {
  264.             RUNGET(c);
  265.             return((RCHAR)'<');
  266.         }
  267.         c2 = RGET(file);
  268.         richtextunget2(c,c2);
  269.         if( !valid_command(c,c2) ){
  270.             /* We have a stray less-than symbol */
  271.                 if (lastch)
  272.                 return(RICHCH_2(lastch,'<'));
  273.                 lastch = '<';
  274.                 continue;    /* Back around for another character */
  275.         }
  276.         }
  277.  
  278.         /* Read a command token from the input file */
  279.         cmd = RICHTEXT_COMMAND;
  280.         if ((c = RGET(file)) == '/') {
  281.             cmd = RICHTEXT_NEG_COMMAND;
  282.             c = RGET(file);
  283.         }
  284.             for (i = 0; i < (MAX_TOKEN_SIZE - 1) && c != '>'
  285.                 && c != EOF && !isspace(c); ++i) {
  286.                 NextToken[i] = isupper(c) ? tolower(c) : c;
  287.             c = RGET(file);
  288.             }
  289.         if (c != '>' && c != EOF && !isspace(c)) {
  290.         /* We have a long command: skip the rest of it */
  291.         while (c != '>' && c != EOF && !isspace(c))
  292.             c = RGET(file);
  293.         }
  294.             if (c == EOF) {
  295.         if (!StackSize)
  296.                 return((RCHAR)EOF);
  297.             /* Flush the remaining commands at the end of the input file */
  298.             FlushSize = StackSize;
  299.             FlushStack = FlushSize;
  300.             EndInpFile = 1;
  301.             return(richtextlex(file,token)); /* Flush something out */
  302.         }
  303.             NextToken[i] = '\0';
  304.  
  305.         /* Process <lt> specially for multi-byte characters */
  306.         if (CharSize > 1 && !strcmp(NextToken,"lt")) {
  307.             if (lastch)
  308.             return(RICHCH_2(lastch,'<'));
  309.             lastch = '<';
  310.             continue;    /* Back around for another character */
  311.         }
  312.  
  313.         /* Check to see if we need to correct anything */
  314.         if (!CorrectionEnabled) {
  315.         /* No correction to do: just skip the correction phase */
  316.         strcpy(token,NextToken);
  317.         return(cmd);
  318.         }
  319.         if (cmd == RICHTEXT_COMMAND) {
  320.         /* Save the command on the stack if not a singleton command */
  321.         if (!richtextsingle()) {
  322.             strcpy (Stack[StackSize++],NextToken);
  323.         }
  324.         }
  325.         else if (!(i = richtextmatchup()))
  326.         continue;    /* No matchup - just drop it */
  327.         else if (i == 1)
  328.         --StackSize;    /* Correct match at the stack top */
  329.         else {
  330.         /* Flush some correction elements from the stack */
  331.         FlushSize = i - 1;
  332.         FlushStack = FlushSize;
  333.         return(richtextlex(file,token));
  334.         }
  335.         strcpy(token,NextToken);
  336.         return(cmd);
  337.         } else if (c == SI) {
  338.         /* Shift-in character: translate to a singleton */
  339.         strcpy(token,ISO2022_SHIFTIN);
  340.         return(RICHTEXT_COMMAND);
  341.     } else if (c == SO) {
  342.         /* Shift-out character: translate to a singleton */
  343.         strcpy(token,ISO2022_SHIFTOUT);
  344.         return(RICHTEXT_COMMAND);
  345.     } else if (c == ESC) {
  346.         /* Check for escape sequences that change character sizes */
  347.         int newc;
  348.         c = RGET(file);
  349.         if (c == '$') {
  350.         newc = RGET(file);
  351.         if (newc == ')') {
  352.             newc = RGET(file);    /* 4-byte ESC-$-)-? sequence */
  353.             sprintf(token,ISO2022_CHARSET,newc);
  354.         } else {
  355.             sprintf(token,ISO2022_GENERIC,newc);
  356.         }
  357.         return(RICHTEXT_COMMAND);
  358.         } else if (c == '(') {
  359.         newc = RGET(file);
  360.         sprintf(token,ISO2022_GENERIC,newc);
  361.         return(RICHTEXT_NEG_COMMAND);
  362.         } else {
  363.         RUNGET(c);
  364.         return((RCHAR)ESC);
  365.         }
  366.         } else if (c == EOF && StackSize) {
  367.         /* Flush the remaining commands at the end of the input file */
  368.         FlushSize = StackSize;
  369.         FlushStack = FlushSize;
  370.         EndInpFile = 1;
  371.         return(richtextlex(file,token)); /* Flush something out */
  372.     } else if (CharSize > 1) {
  373.         /* Recognise a multi-byte character */
  374.         int newc;
  375.         if (!lastch && isspace (c))
  376.           return ((RCHAR)c); /* Hack for spaces in 2-byte modes */
  377.         if (lastch)
  378.         return (RICHCH_2(lastch,c)); /* This is second of 2 chars */
  379.         if ((newc = RGET(file)) == EOF) {
  380.         RUNGET(newc);    /* Push the EOF back into the input stream */
  381.         return((RCHAR)c); /* Just return the partial single-byte char */
  382.         } else if (newc == '<') {
  383.         /* The second character could be "<lt>", so loop around */
  384.         lastch = c;
  385.         RUNGET(newc);
  386.         continue;
  387.         } else {
  388.             return(RICHCH_2(c,newc));
  389.         }
  390.     } else {
  391.         return((RCHAR)c);
  392.     }
  393.     }
  394. }
  395.  
  396. /*
  397.  * Output a string via "RichtextPutc".
  398.  */
  399. static richtextoutstr(str,outparam)
  400. char *str;
  401. void *outparam;
  402. {
  403.     while (*str) {
  404.     RPUT(*str,outparam);
  405.     ++str;
  406.     }
  407. }
  408.  
  409. /*
  410.  * Read the input stream, correct the richtext, and write the
  411.  * results to the output stream.
  412.  */
  413. richtextcorrect(inparam,outparam)
  414. void *inparam,*outparam;
  415. {
  416.     RCHAR c;
  417.     char token[MAX_TOKEN_SIZE];
  418.     while ((c = richtextlex(inparam,token)) != (RCHAR)EOF) {
  419.     if (c == RICHTEXT_COMMAND) {
  420.         RPUT('<',outparam);
  421.         richtextoutstr(token,outparam);
  422.         RPUT('>',outparam);
  423.     } else if (c == RICHTEXT_NEG_COMMAND) {
  424.         RPUT('<',outparam);
  425.         RPUT('/',outparam);
  426.         richtextoutstr(token,outparam);
  427.         RPUT('>',outparam);
  428.     } else if (c >= 256) {
  429.         RPUT(RICHCH2_FIRST(c),outparam);
  430.         RPUT(RICHCH2_SECOND(c),outparam);
  431.     } else {
  432.         RPUT(c,outparam);
  433.     }
  434.     }
  435. }
  436.  
  437. /*
  438.  * Change the encoding used for characters not present in
  439.  * richtext command sequences.
  440.  */
  441. richtextencoding(encoding)
  442. int    encoding;
  443. {
  444.     RichtextCharEncoding = encoding;
  445.     switch (RichtextCharEncoding) {
  446.     case RICH_ENC_US_ASCII:
  447.     case RICH_ENC_JP_ASCII:
  448.     case RICH_ENC_KR_ASCII:
  449.         CharSize = 1;
  450.         break;
  451.  
  452.     case RICH_ENC_JIS_1978:
  453.     case RICH_ENC_JIS_1983:
  454.     case RICH_ENC_KSC_5601:
  455.         CharSize = 2;
  456.         break;
  457.  
  458.     default:CharSize = 1;
  459.         break;
  460.     }
  461. }
  462.